<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>cannon.js - worker example</title>
    <link rel="stylesheet" href="css/style.css" type="text/css" />
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
    <script type="module">
      import { addTitle, addSourceButton } from './js/dom-utils.js'

      addTitle()
      addSourceButton()
    </script>
  </head>
  <body>
    <!-- Worker script, will be run in separate thread -->
    <script id="worker1" type="text/js-worker">
      import * as CANNON from '../dist/cannon-es.js'

      // Number of objects, could be also sent via a
      // first init message, to avoid duplicatoin
      const N = 40

      // The bodies shared with three.js
      const bodies = []

      // Setup world
      const world = new CANNON.World()
      world.gravity.set(0, -10, 0)
      world.solver.tolerance = 0.001

      // Ground plane
      const groundShape = new CANNON.Plane()
      const groundBody = new CANNON.Body({ mass: 0 })
      groundBody.addShape(groundShape)
      groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
      world.addBody(groundBody)

      // Create N cubes
      const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5))
      for (let i = 0; i < N; i++) {
        const body = new CANNON.Body({ mass: 1 })
        body.addShape(boxShape)
        body.position.set(Math.random() - 0.5, i * 2.5 + 0.5, Math.random() - 0.5)
        bodies.push(body)
        world.addBody(body)
      }

      self.addEventListener('message', (event) => {
        const { positions, quaternions, timeStep } = event.data

        // Step the world
        world.step(timeStep)

        // Copy the cannon.js data into the buffers
        for (let i = 0; i < bodies.length; i++) {
          const body = bodies[i]

          positions[i * 3 + 0] = body.position.x
          positions[i * 3 + 1] = body.position.y
          positions[i * 3 + 2] = body.position.z
          quaternions[i * 4 + 0] = body.quaternion.x
          quaternions[i * 4 + 1] = body.quaternion.y
          quaternions[i * 4 + 2] = body.quaternion.z
          quaternions[i * 4 + 3] = body.quaternion.w
        }

        // Send data back to the main thread
        self.postMessage(
          {
            positions,
            quaternions,
          },
          // Specify that we want actually transfer the memory, not copy it over. This is faster.
          [positions.buffer, quaternions.buffer]
        )
      })
    </script>

    <script type="module">
      import * as CANNON from '../dist/cannon-es.js'
      import * as THREE from 'https://unpkg.com/three@0.122.0/build/three.module.js'
      import Stats from 'https://unpkg.com/three@0.122.0/examples/jsm/libs/stats.module.js'
      import { OrbitControls } from 'https://unpkg.com/three@0.122.0/examples/jsm/controls/OrbitControls.js'

      // Parameters
      const timeStep = 1 / 60
      const N = 40 // number of objects

      // Data arrays. Contains all our kinematic data we need for rendering.
      // These will be replaced with the new arrays the worker will send us.
      let positions = new Float32Array(N * 3)
      let quaternions = new Float32Array(N * 4)

      // Get the worker code
      let workerScript = document.querySelector('#worker1').textContent

      // BUG Relative urls don't currently work in an inline
      // module worker in Chrome
      // https://bugs.chromium.org/p/chromium/issues/detail?id=1161710
      const href = window.location.href.replace('/examples/worker', '')
      workerScript = workerScript
        .replace(/from '\.\.\//g, `from '${href}/`)
        .replace(/from '\.\//g, `from '${href}/examples/`)

      // Create a blob for the inline worker code
      const blob = new Blob([workerScript], { type: 'text/javascript' })

      // Create worker
      const worker = new Worker(window.URL.createObjectURL(blob), { type: 'module' })

      // Time when we sent last message
      let sendTime

      // Send the array buffers that will be populated in the
      // worker with cannon.js' data
      function requestDataFromWorker() {
        sendTime = performance.now()
        worker.postMessage(
          {
            timeStep,
            positions,
            quaternions,
          },
          // Specify that we want actually transfer the memory, not copy it over. This is faster.
          [positions.buffer, quaternions.buffer]
        )
      }

      // The mutated position and quaternion data we
      // get back from the worker
      worker.addEventListener('message', (event) => {
        // Get fresh data from the worker
        positions = event.data.positions
        quaternions = event.data.quaternions

        // Update the three.js meshes
        for (let i = 0; i < meshes.length; i++) {
          meshes[i].position.set(positions[i * 3 + 0], positions[i * 3 + 1], positions[i * 3 + 2])
          meshes[i].quaternion.set(
            quaternions[i * 4 + 0],
            quaternions[i * 4 + 1],
            quaternions[i * 4 + 2],
            quaternions[i * 4 + 3]
          )
        }

        // Delay the next step by the amount of timeStep remaining,
        // otherwise run it immediatly
        const delay = timeStep * 1000 - (performance.now() - sendTime)
        setTimeout(requestDataFromWorker, Math.max(delay, 0))
      })

      worker.addEventListener('error', (event) => {
        console.error(event.message)
      })

      // Initialize three.js
      let camera, scene, renderer, stats
      let controls

      // The meshes that will be passed to cannon.js
      const meshes = []

      initThree()
      animate()
      // Start the worker!
      requestDataFromWorker()

      function initThree() {
        // Camera
        camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.5, 10000)
        camera.position.set(Math.cos(Math.PI / 5) * 30, 5, Math.sin(Math.PI / 5) * 30)

        // Scene
        scene = new THREE.Scene()
        scene.fog = new THREE.Fog(0x000000, 500, 10000)

        // Renderer
        renderer = new THREE.WebGLRenderer({ antialias: true })
        renderer.setSize(window.innerWidth, window.innerHeight)
        renderer.setClearColor(scene.fog.color)

        renderer.shadowMap.enabled = true
        renderer.shadowMap.type = THREE.PCFSoftShadowMap

        renderer.outputEncoding = THREE.sRGBEncoding

        document.body.appendChild(renderer.domElement)

        // Stats.js
        stats = new Stats()
        document.body.appendChild(stats.dom)

        // Orbit controls
        controls = new OrbitControls(camera, renderer.domElement)
        controls.enableDamping = true
        controls.enablePan = false
        controls.dampingFactor = 0.3
        controls.minDistance = 10
        controls.maxDistance = 500

        // Lights
        const ambientLight = new THREE.AmbientLight(0x666666)
        scene.add(ambientLight)

        const directionalLight = new THREE.DirectionalLight(0xffffff, 1.75)

        const distance = 20
        directionalLight.position.set(distance, distance, distance)

        directionalLight.castShadow = true

        directionalLight.shadow.mapSize.width = 1024
        directionalLight.shadow.mapSize.height = 1024

        directionalLight.shadow.camera.left = -distance
        directionalLight.shadow.camera.right = distance
        directionalLight.shadow.camera.top = distance
        directionalLight.shadow.camera.bottom = -distance

        directionalLight.shadow.camera.far = 3 * distance
        directionalLight.shadow.camera.near = distance

        scene.add(directionalLight)

        // Floor
        const floorGeometry = new THREE.PlaneBufferGeometry(100, 100, 1, 1)
        floorGeometry.rotateX(-Math.PI / 2)
        const floorMaterial = new THREE.MeshLambertMaterial({ color: 0x777777 })
        const floor = new THREE.Mesh(floorGeometry, floorMaterial)
        floor.receiveShadow = true
        scene.add(floor)

        // Cubes
        const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1, 10, 10)
        const cubeMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 })
        for (let i = 0; i < N; i++) {
          const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial)
          cubeMesh.castShadow = true
          meshes.push(cubeMesh)
          scene.add(cubeMesh)
        }

        window.addEventListener('resize', onWindowResize)
      }

      function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight
        camera.updateProjectionMatrix()
        renderer.setSize(window.innerWidth, window.innerHeight)
      }

      function animate() {
        requestAnimationFrame(animate)

        controls.update()
        renderer.render(scene, camera)
        stats.update()
      }
    </script>
  </body>
</html>
